﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Nature.Data;

namespace Nature.Toolkit.GMix
{
    public interface IDataSet
    {
        int Nsp { get; }

        ReadOnlyArray<string> ElementIds { get; }

        ReadOnlyArray<string> SpeciesIds { get; }

        ReadOnlyArray<double> SpeciesMasses { get; }

        ReadOnlyMatrix<double> Spel { get; }

        void CalcEnthalpies(double[] h, double temperature);

        void CalcEntropies(double[] s, double temperature);

        void CalcCpHeatCapacities(double[] cp, double temperature);
    }

    public class ThermoDataSet : IDataSet
    {
        Thermo7[] m_thermo7;

        public ThermoDataSet(System.Collections.IEnumerable entitiesHeap, IEnumerable<string> speciesIDs)
        {
            SpeciesIds = speciesIDs.ToArray();
            this.Nsp = SpeciesIds.Length;
            var species = (
                from id in SpeciesIds
                let sp = entitiesHeap.OfType<ChemicalSpecies>().Where(i => i.SpeciesID == id).Single()
                select sp).ToList();
            this.SpeciesMasses = species.Select(i => i.SpeciesMolarMass).ToArray();
            this.ElementIds = 
                (
                    from sp in species
                    from apm in sp.AtomsPerMoleculeAmounts
                    select apm.ElementID
                ).Distinct().ToArray();
            m_thermo7 = new Thermo7[Nsp];
            double[][] spel = new double[Nsp][];
            for (int isp = 0; isp < Nsp; ++isp) 
            {
                var sp = species[isp];
                m_thermo7[isp] = sp.Thermo7;
                double[] row = spel[isp] = new double[ElementIds.Length];
                for (int iel = 0; iel < ElementIds.Length; ++iel)
                {
                    row[iel] = sp.AtomsPerMoleculeAmounts
                        .Where(apm => apm.ElementID == ElementIds[iel])
                        .Sum(apm => apm.AtomsPerMolecule);
                }
            }
            this.Spel = spel;
        }

        #region IDataSet Members

        public int Nsp
        {
            get;
            private set;
        }

        public ReadOnlyArray<string> ElementIds
        {
            get;
            private set;
        }

        public ReadOnlyArray<string> SpeciesIds
        {
            get; private set;
        }

        public ReadOnlyArray<double> SpeciesMasses
        {
            get; private set;
        }

        public ReadOnlyMatrix<double> Spel
        {
            get;
            private set;
        }

        public void CalcEnthalpies(double[] h, double temperature)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                h[isp] = m_thermo7[isp].H(temperature);
            }
        }

        public void CalcEntropies(double[] s, double temperature)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                s[isp] = m_thermo7[isp].S(temperature);
            }
        }

        public void CalcCpHeatCapacities(double[] cp, double temperature)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                cp[isp] = m_thermo7[isp].Cp(temperature);
            }
        }

        #endregion
    }

    public static partial class Extensions
    {
        public static double[] ConvertToMassFractions(this IDataSet ds, ReadOnlyArray<double> x)
        {
            double[] y = new double[ds.Nsp];
            ReadOnlyArray<double> w = ds.SpeciesMasses;
            double sum = 0.0;
            for (int isp = 0; isp < ds.Nsp; ++isp)
            {
                double tmp = y[isp] = x[isp] * w[isp];
                sum += tmp;
            }
            for (int isp = 0; isp < ds.Nsp; ++isp)
            {
                y[isp] /= sum;
            }
            return y;
        }

        public static double[] ConvertToMoleFractions(this IDataSet ds, ReadOnlyArray<double> y)
        {
            double[] x = new double[ds.Nsp];
            ReadOnlyArray<double> w = ds.SpeciesMasses;
            double sum = 0.0;
            for (int isp = 0; isp < ds.Nsp; ++isp)
            {
                double tmp = x[isp] = y[isp] / w[isp];
                sum += tmp;
            }
            for (int isp = 0; isp < ds.Nsp; ++isp)
            {
                x[isp] /= sum;
            }
            return x;
        }


        internal static double SpecificEnthalpy(this IDataSet dataSet, CompositionObject composition, double temperature, double pressure)
        {
            throw new NotImplementedException();
        }

        internal static double MolarEnthalpy(this IDataSet dataSet, CompositionObject composition, double temperature, double pressure)
        {
            throw new NotImplementedException();
        }
    }

}
